﻿using System.Collections.Generic;
using UnityEngine;

namespace MeshPickerTool
{
    internal class MeshPicker : MonoBehaviour, IMeshPicker
    {
        private Vector3 uvStartPoint;
        private float uvWidth;
        private float uvHeight;
        private Plane uvPlane;

        public Mesh Pick(MeshFilter meshFilter)
        {
            //Stopwatch watch = new Stopwatch();
            //UnityEngine.Debug.LogError($"面数：{meshFilter.sharedMesh.triangles.Length},顶点数：{meshFilter.sharedMesh.vertices.Length}");
            MeshInfo pickedMeshInfo = new MeshInfo(meshFilter.sharedMesh);
            //UnityEngine.Debug.LogError($"面数：{pickedMeshInfo.triangles.Count},顶点数：{pickedMeshInfo.vertices.Count}");
            PlaneData[] planes = this.CreatePlanes();
            //watch.Start();
            // 转成世界坐标
            for (int index = 0; index < pickedMeshInfo.vertices.Count; index++)
            {
                Vector3 vertex = pickedMeshInfo.vertices[index];
                Vector3 worldPos = meshFilter.transform.TransformPoint(vertex);
                vertex = this.transform.InverseTransformPoint(worldPos);
                pickedMeshInfo.vertices[index] = vertex;
                pickedMeshInfo.normals[index] = this.transform.InverseTransformDirection(meshFilter.transform.TransformDirection(pickedMeshInfo.normals[index]));
            };
            //watch.Stop();
            //UnityEngine.Debug.LogError($"Time:{watch.ElapsedMilliseconds}");
            //watch.Start();

            int triCount = pickedMeshInfo.triangles.Count / 3;
            //for(int i = 0; i < triCount; i++)
            //{

            //}

            //foreach(Plane plane in planes)
            //{
            pickedMeshInfo = this.Split(planes, pickedMeshInfo);
            //}
            //watch.Stop();
            //UnityEngine.Debug.LogError($"Split Time:{watch.ElapsedMilliseconds}");

            //watch.Start();

            // 还原成本地坐标
            for (int index = 0; index < pickedMeshInfo.vertices.Count; index++)
            {
                Vector3 worldVertex = this.transform.TransformPoint(pickedMeshInfo.vertices[index]);

                // 重算uv
                pickedMeshInfo.uvs[index] = this.CalUv(worldVertex, this.uvPlane, this.uvStartPoint, this.uvWidth, this.uvHeight);

                Vector3 vertex = meshFilter.transform.InverseTransformPoint(worldVertex);
                pickedMeshInfo.vertices[index] = vertex;
                pickedMeshInfo.normals[index] = meshFilter.transform.InverseTransformDirection(this.transform.TransformDirection(pickedMeshInfo.normals[index]));
            };
            //watch.Stop();
            //UnityEngine.Debug.LogError($"ToTal Time:{watch.ElapsedMilliseconds}");


            return pickedMeshInfo.GetMesh();
        }

        private GameObject testGo;
        private void DrawPlaneGo(Vector3 normal, Vector3 pos)
        {
            if (testGo == null)
            {
                testGo = new GameObject("CTMP");
            }

            GameObject go = GameObject.CreatePrimitive(PrimitiveType.Plane);
            go.transform.position = pos;
            go.transform.up = normal;
            go.transform.SetParent(testGo.transform, true);
        }
        private void DrawPoint(Vector3 pos, string name = "")
        {
            if (this.testGo == null)
            {
                this.testGo = new GameObject("TestGo");
            }

            GameObject go = GameObject.CreatePrimitive(PrimitiveType.Sphere);
            if (!string.IsNullOrEmpty(name))
            {
                go.name = name;
            }
            go.transform.position = pos;
            go.transform.localScale = 0.01f * Vector3.one;
            go.transform.SetParent(this.testGo.transform, true);
        }

        private PlaneData[] CreatePlanes()
        {
            Bounds bounds = this.GetComponent<MeshFilter>().sharedMesh.bounds;
            PlaneData[] planes = new PlaneData[6];
            Vector3 center = this.transform.TransformPoint(bounds.center);
            Vector3 leftCenter = center - this.transform.right * bounds.extents.x * this.transform.localScale.x;
            Vector3 backCenter = center - this.transform.forward * bounds.extents.z * this.transform.localScale.z;
            Vector3 rightCenter = center + this.transform.right * bounds.extents.x * this.transform.localScale.x;
            Vector3 forwardCenter = center + this.transform.forward * bounds.extents.z * this.transform.localScale.z;
            Vector3 upCenter = center + this.transform.up * bounds.extents.y * this.transform.localScale.y;
            Vector3 downCenter = center - this.transform.up * bounds.extents.y * this.transform.localScale.y;

            this.uvPlane = new Plane(center - backCenter, backCenter);
            this.uvStartPoint = backCenter - this.transform.up * bounds.extents.y * this.transform.localScale.y - this.transform.right * bounds.extents.x * this.transform.localScale.x;
            this.uvWidth = bounds.size.x * this.transform.localScale.x;
            this.uvHeight = bounds.size.y * this.transform.localScale.y;

            Vector3 leftNormal = this.transform.InverseTransformDirection(center - leftCenter);
            Vector3 backNormal = this.transform.InverseTransformDirection(center - backCenter);
            Vector3 rightNormal = this.transform.InverseTransformDirection(center - rightCenter);
            Vector3 forwardNormal = this.transform.InverseTransformDirection(center - forwardCenter);
            Vector3 upNormal = this.transform.InverseTransformDirection(center - upCenter);
            Vector3 downNormal = this.transform.InverseTransformDirection(center - downCenter);

            leftNormal.Scale(this.transform.localScale);
            backNormal.Scale(this.transform.localScale);
            rightNormal.Scale(this.transform.localScale);
            forwardNormal.Scale(this.transform.localScale);
            upNormal.Scale(this.transform.localScale);
            downNormal.Scale(this.transform.localScale);

            planes[0] = new PlaneData() { Normal = leftNormal.normalized, Point = this.transform.InverseTransformPoint(leftCenter) };
            planes[1] = new PlaneData() { Normal = backNormal.normalized, Point = this.transform.InverseTransformPoint(backCenter) };
            planes[2] = new PlaneData() { Normal = rightNormal.normalized, Point = this.transform.InverseTransformPoint(rightCenter) };
            planes[3] = new PlaneData() { Normal = forwardNormal.normalized, Point = this.transform.InverseTransformPoint(forwardCenter) };
            planes[4] = new PlaneData() { Normal = upNormal.normalized, Point = this.transform.InverseTransformPoint(upCenter) };
            planes[5] = new PlaneData() { Normal = downNormal.normalized, Point = this.transform.InverseTransformPoint(downCenter) };

            //this.uvWidth = rightCenter.x
            //planes[0] = (new Plane(center - leftCenter, leftCenter));
            //planes[1] = (uvPlane);
            //planes[2] = (new Plane(center - rightCenter, rightCenter));
            //planes[3] = (new Plane(center - forwardCenter, forwardCenter));
            //planes[4] = (new Plane(center - upCenter, upCenter));
            //planes[5] = (new Plane(center - downCenter, downCenter));

            //this.DrawPlaneGo(center - leftCenter, leftCenter);
            //this.DrawPlaneGo(center - backCenter, backCenter);
            //this.DrawPlaneGo(center - rightCenter, rightCenter);
            //this.DrawPlaneGo(center - forwardCenter, forwardCenter);
            //this.DrawPlaneGo(center - upCenter, upCenter);
            //this.DrawPlaneGo(center - downCenter, downCenter);

            return planes;
        }

        private bool IsPointInside(PlaneData[] planes, Vector3 vert)
        {
            for (int i = 0; i < planes.Length; i++)
            {
                if (Vector3.Dot(vert - planes[i].Point, planes[i].Normal) < 0f)
                {
                    return false;
                }
            }

            return true;
        }

        private MeshInfo Split(PlaneData[] planes, MeshInfo meshInfo)
        {
            MeshInfo picked = new MeshInfo();
            MeshInfo edge = new MeshInfo();
            List<bool> inside = new List<bool>();
            int triangleCount = meshInfo.triangles.Count / 3;

            //Stopwatch w = Stopwatch.StartNew();
            for (int i = 0; i < triangleCount; i++)
            {
                int _i0 = meshInfo.triangles[i * 3];
                int _i1 = meshInfo.triangles[i * 3 + 1];
                int _i2 = meshInfo.triangles[i * 3 + 2];

                Vector3 vert0 = meshInfo.vertices[_i0];
                Vector3 vert1 = meshInfo.vertices[_i1];
                Vector3 vert2 = meshInfo.vertices[_i2];

                Vector3 triangleNormal = (meshInfo.normals[_i0] + meshInfo.normals[_i1] + meshInfo.normals[_i2]).normalized;
                if (Vector3.Dot(this.transform.TransformDirection(triangleNormal), this.transform.forward) >= 0f)
                {
                    continue;
                }
                bool _a0 = this.IsPointInside(planes, vert0);
                bool _a1 = this.IsPointInside(planes, vert1);
                bool _a2 = this.IsPointInside(planes, vert2);
                if (_a0 && _a1 && _a2)
                {
                    picked.triangles.Add(picked.vertices.Count);
                    picked.Add(vert0, meshInfo.uvs[_i0], meshInfo.normals[_i0], meshInfo.tangents[_i0]);
                    picked.triangles.Add(picked.vertices.Count);
                    picked.Add(vert1, meshInfo.uvs[_i1], meshInfo.normals[_i1], meshInfo.tangents[_i1]);
                    picked.triangles.Add(picked.vertices.Count);
                    picked.Add(vert2, meshInfo.uvs[_i2], meshInfo.normals[_i2], meshInfo.tangents[_i2]);
                }
                //else if(!_a0 && !_a1 && !_a2)
                //{
                //    b.triangles.Add(newTriangles[_i0]);
                //    b.triangles.Add(newTriangles[_i1]);
                //    b.triangles.Add(newTriangles[_i2]);
                //}
                else
                {
                    inside.Add(_a0);
                    inside.Add(_a0);
                    inside.Add(_a0);
                    edge.triangles.Add(edge.vertices.Count);
                    edge.Add(vert0, meshInfo.uvs[_i0], meshInfo.normals[_i0], meshInfo.tangents[_i0]);
                    edge.triangles.Add(edge.vertices.Count);
                    edge.Add(vert1, meshInfo.uvs[_i1], meshInfo.normals[_i1], meshInfo.tangents[_i1]);
                    edge.triangles.Add(edge.vertices.Count);
                    edge.Add(vert2, meshInfo.uvs[_i2], meshInfo.normals[_i2], meshInfo.tangents[_i2]);
                }
            }

            //w.Stop();
            //UnityEngine.Debug.LogError($"过滤耗时：{w.ElapsedMilliseconds},面数：{edge.triangles.Count},顶点数：{edge.vertices.Count}");
            //w.Reset();
            //w.Start();
            //for(int i = 0; i < planes.Length; i++)
            //{
            //    edge = this.Split(planes[i], edge);
            //}

            foreach (PlaneData plane in planes)
            {
                edge = this.Split(plane, edge);
            }

            //w.Stop();
            //UnityEngine.Debug.LogError($"q边缘切割耗时：{w.ElapsedMilliseconds}");
            //w.Reset();
            //w.Start();
            picked.Add(edge);
            //w.Stop();
            //UnityEngine.Debug.LogError($" picked.Add耗时：{w.ElapsedMilliseconds}");
            //w.Reset();
            //w.Start();
            //picked.CombineVertices(0.001f);
            picked.center = meshInfo.center;
            picked.size = meshInfo.size;
            //w.Stop();
            //UnityEngine.Debug.LogError($"合并Mesh耗时：{w.ElapsedMilliseconds}");
            return picked;
        }
        private MeshInfo Split(PlaneData plane, MeshInfo edgeMesh)
        {
            Vector3 point = plane.Point;
            Vector3 normal = plane.Normal;
            MeshInfo a = new MeshInfo();
            MeshInfo b = new MeshInfo();
            //
            bool[] above = new bool[edgeMesh.vertices.Count];
            int[] newTriangles = new int[edgeMesh.vertices.Count];

            for (int i = 0; i < newTriangles.Length; i++)
            {
                Vector3 vert = edgeMesh.vertices[i];
                above[i] = Vector3.Dot(vert - point, normal) >= 0f;
                if (above[i])
                {
                    newTriangles[i] = a.vertices.Count;
                    a.Add(vert, edgeMesh.uvs[i], edgeMesh.normals[i], edgeMesh.tangents[i]);
                }
                else
                {
                    newTriangles[i] = b.vertices.Count;
                    b.Add(vert, edgeMesh.uvs[i], edgeMesh.normals[i], edgeMesh.tangents[i]);
                }
            }

            int triangleCount = edgeMesh.triangles.Count / 3;
            for (int i = 0; i < triangleCount; i++)
            {
                int _i0 = edgeMesh.triangles[i * 3];
                int _i1 = edgeMesh.triangles[i * 3 + 1];
                int _i2 = edgeMesh.triangles[i * 3 + 2];

                bool _a0 = above[_i0];
                bool _a1 = above[_i1];
                bool _a2 = above[_i2];
                if (_a0 && _a1 && _a2)
                {
                    a.triangles.Add(newTriangles[_i0]);
                    a.triangles.Add(newTriangles[_i1]);
                    a.triangles.Add(newTriangles[_i2]);
                }
                else if (!_a0 && !_a1 && !_a2)
                {
                    //b.triangles.Add(newTriangles[_i0]);
                    //b.triangles.Add(newTriangles[_i1]);
                    //b.triangles.Add(newTriangles[_i2]);
                }
                else
                {
                    int up, down0, down1;
                    if (_a1 == _a2 && _a0 != _a1)
                    {
                        up = _i0;
                        down0 = _i1;
                        down1 = _i2;
                    }
                    else if (_a2 == _a0 && _a1 != _a2)
                    {
                        up = _i1;
                        down0 = _i2;
                        down1 = _i0;
                    }
                    else
                    {
                        up = _i2;
                        down0 = _i0;
                        down1 = _i1;
                    }
                    Vector3 pos0, pos1;
                    if (above[up])
                        SplitTriangle(edgeMesh, a, b, point, normal, newTriangles, up, down0, down1, out pos0, out pos1);
                    else
                        SplitTriangle(edgeMesh, b, a, point, normal, newTriangles, up, down0, down1, out pos1, out pos0);
                }
            }

            //a.CombineVertices(0.001f);
            a.center = edgeMesh.center;
            a.size = edgeMesh.size;
            return a;
        }

        private void SplitTriangle(MeshInfo total, MeshInfo top, MeshInfo bottom, Vector3 point, Vector3 normal, int[] newTriangles, int up, int down0, int down1, out Vector3 pos0, out Vector3 pos1)
        {
            Vector3 v0 = total.vertices[up];
            Vector3 v1 = total.vertices[down0];
            Vector3 v2 = total.vertices[down1];
            float topDot = Vector3.Dot(point - v0, normal);
            float aScale = Mathf.Clamp01(topDot / Vector3.Dot(v1 - v0, normal));
            float bScale = Mathf.Clamp01(topDot / Vector3.Dot(v2 - v0, normal));
            Vector3 pos_a = v0 + (v1 - v0) * aScale;
            Vector3 pos_b = v0 + (v2 - v0) * bScale;

            Vector2 u0 = total.uvs[up];
            Vector2 u1 = total.uvs[down0];
            Vector2 u2 = total.uvs[down1];
            Vector3 uv_a = (u0 + (u1 - u0) * aScale);
            Vector3 uv_b = (u0 + (u2 - u0) * bScale);

            Vector3 n0 = total.normals[up];
            Vector3 n1 = total.normals[down0];
            Vector3 n2 = total.normals[down1];
            Vector3 normal_a = (n0 + (n1 - n0) * aScale).normalized;
            Vector3 normal_b = (n0 + (n2 - n0) * bScale).normalized;

            Vector4 t0 = total.tangents[up];
            Vector4 t1 = total.tangents[down0];
            Vector4 t2 = total.tangents[down1];
            Vector4 tangent_a = (t0 + (t1 - t0) * aScale).normalized;
            Vector4 tangent_b = (t0 + (t2 - t0) * bScale).normalized;
            tangent_a.w = t1.w;
            tangent_b.w = t2.w;

            int top_a = top.vertices.Count;
            top.Add(pos_a, uv_a, normal_a, tangent_a);
            int top_b = top.vertices.Count;
            top.Add(pos_b, uv_b, normal_b, tangent_b);
            top.triangles.Add(newTriangles[up]);
            top.triangles.Add(top_a);
            top.triangles.Add(top_b);

            int down_a = bottom.vertices.Count;
            bottom.Add(pos_a, uv_a, normal_a, tangent_a);
            int down_b = bottom.vertices.Count;
            bottom.Add(pos_b, uv_b, normal_b, tangent_b);

            bottom.triangles.Add(newTriangles[down0]);
            bottom.triangles.Add(newTriangles[down1]);
            bottom.triangles.Add(down_b);

            bottom.triangles.Add(newTriangles[down0]);
            bottom.triangles.Add(down_b);
            bottom.triangles.Add(down_a);

            pos0 = pos_a;
            pos1 = pos_b;
        }

        private Vector2 CalUv(Vector3 point, Plane projectionPlane, Vector3 startPoint, float width, float height)
        {
            Vector3 projectedVertex = projectionPlane.ClosestPointOnPlane(point);
            Vector3 diff = projectedVertex - startPoint;
            Vector2 uv = new Vector2(Vector3.Dot(diff, this.transform.right) / width, Vector3.Dot(diff, this.transform.up) / height);
            return uv;
        }
    }
}
